Add a Liquid filter `to_xpath`, which quotes a string for use in XPath expression.

Akinori MUSHA 10 years ago
parent
commit
0c490aa82d

+ 19 - 2
app/concerns/liquid_interpolatable.rb

@@ -26,12 +26,29 @@ module LiquidInterpolatable
26 26
   end
27 27
 
28 28
   require 'uri'
29
-  # Percent encoding for URI conforming to RFC 3986.
30
-  # Ref: http://tools.ietf.org/html/rfc3986#page-12
31 29
   module Filters
30
+    # Percent encoding for URI conforming to RFC 3986.
31
+    # Ref: http://tools.ietf.org/html/rfc3986#page-12
32 32
     def uri_escape(string)
33 33
       CGI::escape string
34 34
     end
35
+
36
+    # Escape a string for use in XPath expression
37
+    def to_xpath(string)
38
+      subs = string.scan(/\G(?:\A\z|[^"]+|[^']+)/).map { |x|
39
+        case x
40
+        when /"/
41
+          %Q{'#{x}'}
42
+        else
43
+          %Q{"#{x}"}
44
+        end
45
+      }
46
+      if subs.size == 1
47
+        subs.first
48
+      else
49
+        'concat(' << subs.join(', ') << ')'
50
+      end
51
+    end
35 52
   end
36 53
   Liquid::Template.register_filter(LiquidInterpolatable::Filters)
37 54
 

+ 18 - 0
spec/concerns/liquid_interpolatable_spec.rb

@@ -1,4 +1,5 @@
1 1
 require 'spec_helper'
2
+require 'nokogiri'
2 3
 
3 4
 describe LiquidInterpolatable::Filters do
4 5
   before do
@@ -12,4 +13,21 @@ describe LiquidInterpolatable::Filters do
12 13
       @filter.uri_escape('abc:/?=').should == 'abc%3A%2F%3F%3D'
13 14
     end
14 15
   end
16
+
17
+  describe 'to_xpath' do
18
+    before do
19
+      def @filter.to_xpath_roundtrip(string)
20
+        Nokogiri::XML('').xpath(to_xpath(string))
21
+      end
22
+    end
23
+
24
+    it 'should escape a string for use in XPath expression' do
25
+      [
26
+        %q{abc}.freeze,
27
+        %q{'a"bc'dfa""fds''fa}.freeze,
28
+      ].each { |string|
29
+        @filter.to_xpath_roundtrip(string).should == string
30
+      }
31
+    end
32
+  end
15 33
 end

+ 1 - 1
spec/models/agents/website_agent_spec.rb

@@ -471,7 +471,7 @@ fire: hot
471 471
 
472 472
         lambda {
473 473
           @valid_options['extract']['site_title'] = {
474
-            'css' => "#comic img", 'value' => "'{{title}}'"
474
+            'css' => "#comic img", 'value' => "{{title | to_xpath }}"
475 475
           }
476 476
           @checker.options = @valid_options
477 477
           @checker.receive([@event])